﻿#define CSharp4

// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using BrainTechLLC;
using System.Windows.Threading;
using System.Windows;
using BrainTechLLC.ThreadSafeObjects;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
using System.Globalization;
using System.Dynamic;
using Expression = System.Linq.Expressions.Expression;

namespace BrainTechLLC.WPF
{
    [Serializable]
    [DataContract]
    public partial class ViewModelBase : BaseNotifyPropertyChangedUI
#if CSharp4
, IDynamicMetaObjectProvider
#endif
    {
        protected object _Object;

        public object Object
        {
            get { return _Object; }
        }

#if CSharp4
        #region IDynamicMetaObjectProvider Members

        public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new CustomMetaObject(parameter, this);
        }

        #endregion
#endif

        public virtual void OnItemRaisedPropertyChanged(string propertyName)
        {
        }

        public object GetValue(object o, string propertyName)
        {
            object value;
            ViewModelBase vm = o as ViewModelBase;
            Type t = o.GetType();
            PropertyInfo pi = t.GetProperty(propertyName);
            if (pi == null)
            {
                t = vm.Object.GetType();
                pi = t.GetProperty(propertyName);

                if (pi == null)
                {
                    throw new Exception(string.Format("Property {0} not found", propertyName));
                }

                value = pi.GetValue(vm.Object, null);
            }
            else
            {
                value = pi.GetValue(o, null);
            }

            if (value == null)
            {
                var customAttribs = pi.ReadAttribute<ReturnValueWhenNullAttribute>();

                if (customAttribs != null && customAttribs.Length > 0)
                {
                    value = customAttribs[0].Val;
                }
            }

            return value;
        }

        public object DynamicSetValue(object o, string propertyName, object value)
        {
            ViewModelBase vm = o as ViewModelBase;
            Type t = o.GetType();
            PropertyInfo pi = t.GetProperty(propertyName);
            if (pi == null)
            {
                t = vm.Object.GetType();
                pi = t.GetProperty(propertyName);

                if (pi == null)
                {
                    throw new Exception(string.Format("Property {0} not found", propertyName));
                }

                object val = Convert.ChangeType(value, pi.PropertyType, CultureInfo.InvariantCulture);
                pi.SetValue(vm.Object, val, null);
            }
            else
            {
                object val = Convert.ChangeType(value, pi.PropertyType, CultureInfo.InvariantCulture);
                pi.SetValue(o, val, null);
            }

            return value;
        }
    }

    [Serializable]
    [DataContract]
    public partial class GenericViewModelBase<TViewModel, T> : ViewModelBase
        where T : BaseNotifyPropertyChanged
        where TViewModel : BaseNotifyPropertyChanged
    {
        public static ThreadSafeListAndLookup<string, string> _alsoRaisePropertiesLookup;

        static GenericViewModelBase()
        {
            _alsoRaisePropertiesLookup = new ThreadSafeListAndLookup<string, string>();
            typeof(T).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
            typeof(TViewModel).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
        }

        protected T _Item;

        public T Item
        {
            get { return _Item; }
            set
            {
                if (_Item != value)
                {
                    if (_Item != null)
                    {
                        _Item.PropertyChanged -= _Item_PropertyChanged;
                    }

                    _Item = value;
                    _Object = value;

                    if (_Item != null)
                    {
                        _Item.PropertyChanged += new PropertyChangedEventHandler(_Item_PropertyChanged);
                    }

                    OnPropertyChangedUIThread("Item");
                }
            }
        }

        protected void _Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChangedUIThread(e.PropertyName);
            OnItemRaisedPropertyChanged(e.PropertyName);
        }

        public GenericViewModelBase()
        {
            this.PropertyChanged += new PropertyChangedEventHandler(GenericViewModelBase_PropertyChanged);
        }

        public GenericViewModelBase(T item)
            : this()
        {
            Item = item;
        }

        void GenericViewModelBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != null)
            {
                this.CheckAlsoRaise(e.PropertyName, _alsoRaisePropertiesLookup);
            }
        }

        public override void OnItemRaisedPropertyChanged(string propertyName)
        {
            if (propertyName != null)
            {
                this.CheckAlsoRaise(propertyName, _alsoRaisePropertiesLookup);
            }

            base.OnItemRaisedPropertyChanged(propertyName);
        }
    }

    [Serializable]
    [DataContract]
    public partial class MultiGenericViewModelBase<TViewModel, T1, T2> : MultiViewModelBase
        where T1 : BaseNotifyPropertyChanged
        where T2 : BaseNotifyPropertyChanged
        where TViewModel : BaseNotifyPropertyChanged
    {
        public static ThreadSafeListAndLookup<string, string> _alsoRaisePropertiesLookup;

        static MultiGenericViewModelBase()
        {
            _alsoRaisePropertiesLookup = new ThreadSafeListAndLookup<string, string>();
            typeof(T1).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
            typeof(T2).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
            typeof(TViewModel).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
        }

        protected T1 _Item1;

        public T1 Item1
        {
            get { return _Item1; }
            set
            {
                if (_Item1 != value)
                {
                    if (_Item1 != null)
                    {
                        _Item1.PropertyChanged -= _Item1_PropertyChanged;
                    }

                    _Item1 = value;
                    _Object[0] = value;

                    if (_Item1 != null)
                    {
                        _Item1.PropertyChanged += new PropertyChangedEventHandler(_Item1_PropertyChanged);
                    }

                    OnPropertyChangedUIThread("Item1");
                }
            }
        }

        protected T2 _Item2;

        public T2 Item2
        {
            get { return _Item2; }
            set
            {
                if (_Item2 != value)
                {
                    if (_Item2 != null)
                    {
                        _Item2.PropertyChanged -= _Item2_PropertyChanged;
                    }

                    _Item2 = value;
                    _Object[1] = value;

                    if (_Item2 != null)
                    {
                        _Item2.PropertyChanged += new PropertyChangedEventHandler(_Item2_PropertyChanged);
                    }

                    OnPropertyChangedUIThread("Item2");
                }
            }
        }

        protected void _Item1_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChangedUIThread(e.PropertyName);
            OnItemRaisedPropertyChanged(e.PropertyName);
        }

        protected void _Item2_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChangedUIThread(e.PropertyName);
            OnItemRaisedPropertyChanged(e.PropertyName);
        }

        public MultiGenericViewModelBase()
        {
            this.PropertyChanged += new PropertyChangedEventHandler(MultiGenericViewModelBase_PropertyChanged);
        }

        public MultiGenericViewModelBase(T1 item1, T2 item2)
            : this()
        {
            _Object = new List<object>(2);
            _Object.Add(item1);
            _Object.Add(item2);
            Item1 = item1;
            Item2 = item2;
        }

        void MultiGenericViewModelBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != null)
            {
                this.CheckAlsoRaise(e.PropertyName, _alsoRaisePropertiesLookup);
            }
        }

        public override void OnItemRaisedPropertyChanged(string propertyName)
        {
            if (propertyName != null)
            {
                this.CheckAlsoRaise(propertyName, _alsoRaisePropertiesLookup);
            }

            base.OnItemRaisedPropertyChanged(propertyName);
        }
    }

    [Serializable]
    [DataContract]
    public partial class MultiGenericViewModelBase<TViewModel, T1, T2, T3> : MultiViewModelBase
        where T1 : BaseNotifyPropertyChanged
        where T2 : BaseNotifyPropertyChanged
        where T3 : BaseNotifyPropertyChanged
        where TViewModel : BaseNotifyPropertyChanged
    {
        public static ThreadSafeListAndLookup<string, string> _alsoRaisePropertiesLookup;

        static MultiGenericViewModelBase()
        {
            _alsoRaisePropertiesLookup = new ThreadSafeListAndLookup<string, string>();
            typeof(T1).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
            typeof(T2).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
            typeof(T3).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
            typeof(TViewModel).WireUpAlsoRaisePropertiesLookup(_alsoRaisePropertiesLookup);
        }

        protected T1 _Item1;

        public T1 Item1
        {
            get { return _Item1; }
            set
            {
                if (_Item1 != value)
                {
                    if (_Item1 != null)
                    {
                        _Item1.PropertyChanged -= _Item1_PropertyChanged;
                    }

                    _Item1 = value;
                    _Object[0] = value;

                    if (_Item1 != null)
                    {
                        _Item1.PropertyChanged += new PropertyChangedEventHandler(_Item1_PropertyChanged);
                    }

                    OnPropertyChangedUIThread("Item1");
                }
            }
        }

        protected T2 _Item2;

        public T2 Item2
        {
            get { return _Item2; }
            set
            {
                if (_Item2 != value)
                {
                    if (_Item2 != null)
                    {
                        _Item2.PropertyChanged -= _Item2_PropertyChanged;
                    }

                    _Item2 = value;
                    _Object[1] = value;

                    if (_Item2 != null)
                    {
                        _Item2.PropertyChanged += new PropertyChangedEventHandler(_Item2_PropertyChanged);
                    }

                    OnPropertyChangedUIThread("Item2");
                }
            }
        }

        protected T3 _Item3;

        public T3 Item3
        {
            get { return _Item3; }
            set
            {
                if (_Item3 != value)
                {
                    if (_Item3 != null)
                    {
                        _Item3.PropertyChanged -= _Item3_PropertyChanged;
                    }

                    _Item3 = value;
                    _Object[1] = value;

                    if (_Item3 != null)
                    {
                        _Item3.PropertyChanged += new PropertyChangedEventHandler(_Item3_PropertyChanged);
                    }

                    OnPropertyChangedUIThread("Item3");
                }
            }
        }

        protected void _Item1_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChangedUIThread(e.PropertyName);
            OnItemRaisedPropertyChanged(e.PropertyName);
        }

        protected void _Item2_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChangedUIThread(e.PropertyName);
            OnItemRaisedPropertyChanged(e.PropertyName);
        }

        protected void _Item3_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChangedUIThread(e.PropertyName);
            OnItemRaisedPropertyChanged(e.PropertyName);
        }

        public MultiGenericViewModelBase()
        {
            this.PropertyChanged += new PropertyChangedEventHandler(MultiGenericViewModelBase_PropertyChanged);
        }

        public MultiGenericViewModelBase(T1 item1, T2 item2, T3 item3)
            : this()
        {
            _Object = new List<object>(2);
            _Object.Add(item1);
            _Object.Add(item2);
            _Object.Add(item3);
            Item1 = item1;
            Item2 = item2;
            Item3 = item3;
        }

        void MultiGenericViewModelBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != null)
            {
                this.CheckAlsoRaise(e.PropertyName, _alsoRaisePropertiesLookup);
            }
        }

        public override void OnItemRaisedPropertyChanged(string propertyName)
        {
            if (propertyName != null)
            {
                this.CheckAlsoRaise(propertyName, _alsoRaisePropertiesLookup);
            }

            base.OnItemRaisedPropertyChanged(propertyName);
        }
    }

    [Serializable]
    [DataContract]
    public partial class MultiViewModelBase : BaseNotifyPropertyChangedUI
#if CSharp4
, IDynamicMetaObjectProvider
#endif
    {
        protected List<object> _Object = new List<object>();

        public List<object> Object
        {
            get { return _Object; }
        }

#if CSharp4
        #region IDynamicMetaObjectProvider Members

        public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter)
        {
            return new CustomMetaObject(parameter, this);
        }

        #endregion
#endif

        public virtual void OnItemRaisedPropertyChanged(string propertyName)
        {
        }

        public object GetValue(object o, string propertyName)
        {
            object value;
            MultiViewModelBase vm = o as MultiViewModelBase;
            Type t = o.GetType();
            PropertyInfo pi = t.GetProperty(propertyName);
            if (pi == null)
            {
                object objectFound = null;
                for (int n = 0; n < vm.Object.Count; n++)
                {
                    var obj = vm.Object[n];
                    if (obj == null)
                        continue;

                    t = obj.GetType();
                    pi = t.GetProperty(propertyName);

                    if (pi != null)
                    {
                        objectFound = vm.Object[n];
                        break;
                    }
                }

                if (pi == null)
                {
                    throw new Exception(string.Format("Property {0} not found", propertyName));
                }

                value = pi.GetValue(objectFound, null);
            }
            else
            {
                value = pi.GetValue(o, null);
            }

            if (value == null)
            {
                var customAttribs = pi.ReadAttribute<ReturnValueWhenNullAttribute>();

                if (customAttribs != null && customAttribs.Length > 0)
                {
                    value = customAttribs[0].Val;
                }
            }

            return value;
        }

        public object DynamicSetValue(object o, string propertyName, object value)
        {
            MultiViewModelBase vm = o as MultiViewModelBase;
            Type t = o.GetType();
            PropertyInfo pi = t.GetProperty(propertyName);
            if (pi == null)
            {
                object objectFound = null;
                for (int n = 0; n < vm.Object.Count; n++)
                {
                    var obj = vm.Object[n];
                    if (obj == null)
                        continue;

                    t = obj.GetType();
                    pi = t.GetProperty(propertyName);

                    if (pi != null)
                    {
                        objectFound = vm.Object[n];
                        break;
                    }
                }

                if (pi == null)
                {
                    throw new Exception(string.Format("Property {0} not found", propertyName));
                }

                object val = Convert.ChangeType(value, pi.PropertyType, CultureInfo.InvariantCulture);
                pi.SetValue(objectFound, val, null);
            }
            else
            {
                object val = Convert.ChangeType(value, pi.PropertyType, CultureInfo.InvariantCulture);
                pi.SetValue(o, val, null);
            }

            return value;
        }
    }

#if CSharp4

    public class CustomMetaObject : DynamicMetaObject
    {
        public CustomMetaObject(Expression parameter, object value) : base(parameter, BindingRestrictions.Empty, value) { }

        public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
        {
            var self = this.Expression;
            Type type = base.Value.GetType();

            UnaryExpression unarySet = Expression.Convert(self, type);
            Expression target;

            if (unarySet.Method == null && typeof(ViewModelBase).IsAssignableFrom(type))
            {
                ViewModelBase vm = base.Value as ViewModelBase;
                MethodInfo mi = type.GetMethod("DynamicSetValue", BindingFlags.Instance | BindingFlags.Public); // | BindingFlags.FlattenHierarchy
                var propertyNameExpression = Expression.Constant(binder.Name);
                var thisVarExpression = Expression.Constant(base.Value);
                var valueExpression = Expression.Convert(value.Expression, typeof(object));
                target = Expression.Call(Expression.Convert(self, type), mi, thisVarExpression, propertyNameExpression, valueExpression);
            }
            else if (unarySet.Method == null && typeof(MultiViewModelBase).IsAssignableFrom(type))
            {
                MultiViewModelBase vm = base.Value as MultiViewModelBase;
                MethodInfo mi = type.GetMethod("DynamicSetValue", BindingFlags.Instance | BindingFlags.Public); // | BindingFlags.FlattenHierarchy
                var propertyNameExpression = Expression.Constant(binder.Name);
                var thisVarExpression = Expression.Constant(base.Value);
                var valueExpression = Expression.Convert(value.Expression, typeof(object));
                target = Expression.Call(Expression.Convert(self, type), mi, thisVarExpression, propertyNameExpression, valueExpression);
            }
            else
            {
                target = Expression.Call(unarySet, null);
            }

            var restrictions = BindingRestrictions.GetInstanceRestriction(self, base.Value);
            return new DynamicMetaObject(target, restrictions);
        }

        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            Expression self = this.Expression;
            Type type = base.Value.GetType();

            UnaryExpression unaryGet = Expression.Convert(self, type);
            Expression target;

            if (unaryGet.Method == null && typeof(ViewModelBase).IsAssignableFrom(type))
            {
                ViewModelBase vm = base.Value as ViewModelBase;
                MethodInfo mi = type.GetMethod("GetValue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
                var propertyNameExpression = Expression.Constant(binder.Name);
                var thisVarExpression = Expression.Constant(base.Value);
                target = Expression.Call(Expression.Convert(self, type), mi, thisVarExpression, propertyNameExpression);
            }
            else if (unaryGet.Method == null && typeof(MultiViewModelBase).IsAssignableFrom(type))
            {
                MultiViewModelBase vm = base.Value as MultiViewModelBase;
                MethodInfo mi = type.GetMethod("GetValue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
                var propertyNameExpression = Expression.Constant(binder.Name);
                var thisVarExpression = Expression.Constant(base.Value);
                target = Expression.Call(Expression.Convert(self, type), mi, thisVarExpression, propertyNameExpression);
            }
            else
            {
                target = Expression.Call(unaryGet, null);
            }

            var restrictions = BindingRestrictions.GetInstanceRestriction(self, base.Value);
            return new DynamicMetaObject(target, restrictions);
        }
    }

#endif
}
